// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Task.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the Task type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Threading
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Threading;
    using EventPattern;

    public class Task : ITask
    {
        #region Fields

        private static int _currentTaskId;
        private static TaskFactory _factory;
        private ITaskBinding _binding;
        private TaskCallBack _callBack;
        protected volatile ManualResetEvent _completionEvent;
        private TaskContinuationSource _continuationSource;
        protected Exception _error;
        private int _flags;
        protected TaskScheduler _scheduler;
        private int _taskId;
        private object _asyncState;

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="Task"/> class.
        /// </summary>
        /// <param name="scheduler">The scheduler.</param>
        public Task(TaskScheduler scheduler)
            : this(null, scheduler)
        {
        }

        public Task(ITaskBinding binding, TaskScheduler scheduler)
        {
            if (binding != null)
            {
                Bind(binding);
            }

            // use default scheduler if none  is specified.
            _scheduler = scheduler ?? TaskScheduler.Default;
        }

        public static TaskFactory Factory
        {
            get
            {
                return LazyInitializer.EnsureInitialized(
                    ref _factory, () => new TaskFactory(TaskScheduler.Default));
            }
        }

        private WaitHandle CompletionEvent
        {
            get
            {
#pragma warning disable 420
                return LazyInitializer.EnsureInitialized(ref _completionEvent,
                    () => new ManualResetEvent(false));
#pragma warning restore 420
            }
        }

        public int Id
        {
            get
            {
                if (_taskId == 0)
                {
                    int num;
                    do
                    {
                        num = Interlocked.Increment(ref _currentTaskId);
                    } 
                    while (num == 0);

                    Interlocked.CompareExchange(ref _taskId, num, 0);
                }
                return _taskId;
            }
        }

        #region ITask Members

        public void Start(TaskCallBack callBack, object userState)
        {
            // attempt to transition to the Started state.
            if (!SetFlagsIfPermitted(TaskFlags.IsStarted | TaskFlags.IsContinuation, TaskFlags.IsStarted))
            {
                if (IsFlagSet(TaskFlags.IsStarted))
                {
                    throw TaskExceptions.CannotCallStartIfAlreadyStarted();
                }

                throw TaskExceptions.CannotCallStartIfContinuation();
            }
            Initialize(callBack, userState);

            // schedule
            Schedule();
        }

        public Exception Error
        {
            get
            {
                WaitCore();
                return _error;
            }
        }

        public bool IsCanceled
        {
            get { return IsFlagSet(TaskFlags.IsCanceled); }
        }

        public object AsyncState
        {
            get { return _asyncState; }
        }

        public bool IsComplete
        {
            get { return IsFlagSet(TaskFlags.IsComplete); }
        }

        public void Wait()
        {
            WaitCore();
            ThrowIfError();
        }

        public void ContinueFrom(IContinuationSource source)
        {
            ContinueFrom(source, null, null);
        }

        public void ContinueFrom(IContinuationSource source, TaskCallBack callBack, object asyncState)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }

            if (!SetFlagsIfPermitted(TaskFlags.IsStarted | TaskFlags.IsContinuation, TaskFlags.IsContinuation))
            {
                if (IsFlagSet(TaskFlags.IsStarted))
                {
                    throw TaskExceptions.CannotCallContinueFromIfStarted();
                }

                throw TaskExceptions.CannotCallContinueFromIfAlreadyContinuation();
            }
            Initialize(callBack, asyncState);

            source.ContinueWith(Schedule);
        }

        public IContinuationSource ContinuationSource
        {
            get
            {
                TaskContinuationSource result;
                if (LazyInitializer.EnsureInitialized(ref _continuationSource,
                    () => new TaskContinuationSource(), out result))
                {
                    // notify if we have completed since we initialized (Deadlock).
                    if (IsComplete)
                    {
                        result.NotifyComplete();
                    }
                }

                return result;
            }
        }

        #endregion

        private bool IsFlagSet(int flag)
        {
            return (_flags & flag) == flag;
        }

        private void SetFlags(int value)
        {
            SetFlagsIfPermitted(0, value);
        }

        private bool SetFlagsIfPermitted(int illegalFlags, int value)
        {
            while (true)
            {
                var oldFlags = _flags;
                if (illegalFlags != 0 && (oldFlags & illegalFlags) != 0)
                {
                    return false;
                }

                if (Interlocked.CompareExchange(ref _flags, oldFlags | value, oldFlags) == oldFlags)
                {
                    return true;
                }

                Thread.SpinWait(1);
            }
        }

        private void Schedule()
        {
            EnsureBinding();
            if (_binding.IsAsync)
            {
                _scheduler.QueueAsyncInvoke(BeginCore);
            }
            else
            {
                _scheduler.QueueInvoke(BeginCore);
            }
        }

        private void Initialize(TaskCallBack callBack, object asyncState)
        {
            _callBack = callBack;
            _asyncState = asyncState;
        }

        private void ThrowIfError()
        {
            if (Error != null)
            {
                throw Error;
            }
        }

        private void WaitCore()
        {
            if (!IsComplete)
            {
                var handle = CompletionEvent;

                // must check IsComplete again, otherwise Deadlock is possible
                if (!IsComplete)
                {
                    handle.WaitOne();
                }
            }
        }

        private void Complete(Exception error, bool isCanceled)
        {
            // save error
            _error = error;

            // update state flags
            var flags = TaskFlags.IsComplete;
            if (isCanceled || IsCancellationError(error))
            {
                flags |= TaskFlags.IsCanceled;
            }

            SetFlags(flags);

            // notify
            NotifyComplete();
        }

        private void NotifyComplete()
        {
            // signal wait handle
            if (_completionEvent != null)
            {
                _completionEvent.Set();
            }

            // dispatch callback
            if (_callBack != null)
            {
                if (_binding.IsAsync)
                {
                    _scheduler.QueueAsyncReturn(() => _callBack(this));
                }
                else
                {
                    _scheduler.QueueReturn(() => _callBack(this));
                }
            }

            // fire continuations
            if (_continuationSource != null)
            {
                _continuationSource.NotifyComplete();
            }
        }

        private static bool IsCancellationError(Exception error)
        {
            return error != null && typeof(OperationCanceledException).IsAssignableFrom(error.GetType());
        }

        private void BeginCore()
        {
            _binding.Begin(EndCore);
        }

        private void EndCore(IAsyncResult ar)
        {
            Exception error = null;
            try
            {
                _binding.End(ar);
            }
            catch (Exception e)
            {
                error = e;
            }
            Complete(error, false);
        }

        /// <summary>
        /// Binds the task to the value returned if the task has not aready been bound.
        /// </summary>
        protected void EnsureBinding()
        {
            if (!IsFlagSet(TaskFlags.IsBound))
            {
                Bind(CreateBindingCore());
            }
        }

        /// <summary>
        /// Core method for creating bindings.
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// Marked as internal to allow <see cref="Task{TReturn}"/> to 
        /// adapt late bound bindings.
        /// </remarks>
        internal virtual ITaskBinding CreateBindingCore()
        {
            return CreateBinding();
        }

        /// <summary>
        /// Binds the task using the specified <see cref="ITaskBinding"/>
        /// </summary>
        /// <param name="binding">The <see cref="ITaskBinding{TReturn}"/> for the task.</param>
        internal void Bind(ITaskBinding binding)
        {
            #region Validate Arguments

            if (binding == null)
            {
                throw new ArgumentNullException("binding");
            }

            #endregion

            // can only be bound once
            if (!SetFlagsIfPermitted(TaskFlags.IsBound, TaskFlags.IsBound))
            {
                throw TaskExceptions.CannotCallBindIfAlreadyBound();
            }
            _binding = binding;
        }

        /// <summary>
        /// Creates the binding for the task.
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// Called during dispatch if the task has not already been bound. 
        /// Descendents can override this method to implement late bound 
        /// tasks. This is useful when authoring Command Tasks.
        /// </remarks>
        protected virtual ITaskBinding CreateBinding()
        {
            throw new NotImplementedException();
        }

        #region Nested type: TaskContinuationSource

        private class TaskContinuationSource : ContinuationSource
        {
            public void NotifyComplete()
            {
                Completed();
            }
        }

        #endregion

        #region Nested type: TaskFlags

        private static class TaskFlags
        {
            public const int None = 0;
            public const int IsStarted = 1;
            public const int IsContinuation = 2;
            public const int IsBound = 4;
            public const int IsComplete = 8;
            public const int IsCanceled = 16;
        }

        #endregion
    }

    [SuppressMessage("Microsoft.StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass",
        Justification = "Reviewed. Suppression is OK here.")]
    public class Task<TReturn> : Task, ITask<TReturn>
    {
        #region Fields

        private TReturn _result;

        #endregion

        public Task(TaskScheduler scheduler)
            : base(scheduler)
        {
        }

        public Task(ITaskBinding<TReturn> binding, TaskScheduler scheduler)
            : base(scheduler)
        {
            Bind(new TaskBindingAdapter(this, binding));
        }

        #region ITask<TReturn> Members

        public void Start(TaskCallBack<TReturn> callBack, object userState)
        {
            // wrap callback
            TaskCallBack wrappedCallback = null;
            if (callBack != null)
            {
                wrappedCallback = r => callBack((ITask<TReturn>)r);
            }

            // delegate to base
            Start(wrappedCallback, userState);
        }

        public TReturn Result
        {
            get
            {
                Wait();
                return _result;
            }
        }

        #endregion

        internal override ITaskBinding CreateBindingCore()
        {
            return new TaskBindingAdapter(this, CreateBinding());
        }

        protected new virtual ITaskBinding<TReturn> CreateBinding()
        {
            throw new NotImplementedException();
        }

        #region Nested type: TaskBindingAdapter

        private class TaskBindingAdapter : ITaskBinding
        {
            #region Fields

            private readonly ITaskBinding<TReturn> _binding;
            private readonly Task<TReturn> _task;

            #endregion

            public TaskBindingAdapter(Task<TReturn> task, ITaskBinding<TReturn> binding)
            {
                _task = task;
                _binding = binding;
            }

            #region ITaskBinding Members

            public IAsyncResult Begin(AsyncCallback callBack)
            {
                return _binding.Begin(callBack);
            }

            public void End(IAsyncResult asyncResult)
            {
                _task._result = _binding.End(asyncResult);
            }

            public bool IsAsync
            {
                get { return _binding.IsAsync; }
            }

            #endregion
        }

        #endregion
    }
}